get.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
  2. import { z } from '@hono/zod-openapi';
  3. import { AppDataSource } from '@/server/data-source';
  4. import { StockDataService } from '@/server/modules/stock/stock-data.service';
  5. import { AuthContext } from '@/server/types/context';
  6. import { authMiddleware } from '@/server/middleware/auth.middleware';
  7. import { ErrorSchema } from '@/server/utils/errorHandler';
  8. import dayjs from 'dayjs';
  9. // 路径参数Schema
  10. const GetStockHistoryParams = z.object({
  11. code: z.string().openapi({
  12. param: { name: 'code', in: 'path' },
  13. example: '001339',
  14. description: '股票代码'
  15. })
  16. });
  17. // 股票历史数据项Schema
  18. const StockHistoryItemSchema = z.object({
  19. d: z.string().openapi({
  20. description: '日期',
  21. example: '2007-09-13'
  22. }),
  23. o: z.number().openapi({
  24. description: '开盘价',
  25. example: 40.99
  26. }),
  27. h: z.number().openapi({
  28. description: '最高价',
  29. example: 59
  30. }),
  31. l: z.number().openapi({
  32. description: '最低价',
  33. example: 40.99
  34. }),
  35. c: z.number().openapi({
  36. description: '收盘价',
  37. example: 53.11
  38. }),
  39. v: z.number().openapi({
  40. description: '成交量',
  41. example: 78854
  42. }),
  43. e: z.number().openapi({
  44. description: '成交额',
  45. example: 369834688.13
  46. }),
  47. zf: z.number().openapi({
  48. description: '振幅',
  49. example: 202.13
  50. }),
  51. hs: z.number().openapi({
  52. description: '换手率',
  53. example: 78.85
  54. }),
  55. zd: z.number().openapi({
  56. description: '涨跌幅',
  57. example: 496.07
  58. }),
  59. // pc: z.number().openapi({
  60. // description: '涨跌额',
  61. // example: 44.2
  62. // }),
  63. ud: z.string().openapi({
  64. description: '最后更新时间',
  65. example: '2025-06-29 02:13:11'
  66. })
  67. });
  68. // 响应Schema
  69. const GetStockHistoryResponse = z.object({
  70. data: z.array(StockHistoryItemSchema).openapi({
  71. description: '股票历史K线数据列表',
  72. example: [
  73. {
  74. d: "2007-09-13",
  75. o: 40.99,
  76. h: 59,
  77. l: 40.99,
  78. c: 53.11,
  79. v: 78854,
  80. e: 369834688.13,
  81. zf: 202.13,
  82. hs: 78.85,
  83. zd: 496.07,
  84. zde: 44.2,
  85. ud: "2025-06-29 02:13:11"
  86. }
  87. ]
  88. })
  89. });
  90. // 路由定义
  91. const routeDef = createRoute({
  92. method: 'get',
  93. path: '/{code}',
  94. middleware: [authMiddleware],
  95. request: {
  96. params: GetStockHistoryParams
  97. },
  98. responses: {
  99. 200: {
  100. description: '成功获取股票历史数据',
  101. content: { 'application/json': { schema: GetStockHistoryResponse } }
  102. },
  103. 400: {
  104. description: '请求参数错误',
  105. content: { 'application/json': { schema: ErrorSchema } }
  106. },
  107. 500: {
  108. description: '服务器错误',
  109. content: { 'application/json': { schema: ErrorSchema } }
  110. }
  111. }
  112. });
  113. // 路由实现
  114. const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
  115. try {
  116. const { code } = c.req.valid('param');
  117. const service = new StockDataService(AppDataSource);
  118. const rawData = await service.getStockHistory(code);
  119. // 转换服务输出格式为schema要求的格式
  120. const formattedData = rawData.map(item => ({
  121. d: dayjs(item.t).format('YYYY-MM-DD') , // 交易时间 -> 日期
  122. o: item.o, // 开盘价
  123. h: item.h, // 最高价
  124. l: item.l, // 最低价
  125. c: item.c, // 收盘价
  126. v: item.v, // 成交量
  127. e: item.a, // 成交额 -> 成交额
  128. zf: 0, // 振幅 (计算值)
  129. hs: 0, // 换手率 (计算值)
  130. zd: 0, // 涨跌幅 (计算值)
  131. // pc: 0, // 涨跌额 (计算值)
  132. ud: new Date().toISOString() // 最后更新时间
  133. }));
  134. // 计算衍生字段
  135. const processedData = formattedData.map((item, index, array) => {
  136. if (index === 0) {
  137. // 第一条数据无法计算涨跌
  138. return {
  139. ...item,
  140. zd: 0,
  141. // pc: 0,
  142. zf: 0
  143. };
  144. }
  145. const prevItem = array[index - 1];
  146. const pc = item.c - prevItem.c; // 涨跌额
  147. const zd = prevItem.c !== 0 ? (pc / prevItem.c) * 100 : 0; // 涨跌幅百分比
  148. const zf = prevItem.c !== 0 ? ((item.h - item.l) / prevItem.c) * 100 : 0; // 振幅百分比
  149. return {
  150. ...item,
  151. zd: parseFloat(zd.toFixed(2)),
  152. // pc: parseFloat(pc.toFixed(2)),
  153. zf: parseFloat(zf.toFixed(2))
  154. };
  155. });
  156. // 设置换手率为0(需要从其他数据源获取)
  157. const finalData = processedData.map(item => ({
  158. ...item,
  159. hs: 0
  160. }));
  161. return c.json({ data: finalData }, 200);
  162. } catch (error) {
  163. const { code = 500, message = '获取股票历史数据失败' } = error as Error & { code?: number };
  164. return c.json({ code, message }, 500);
  165. }
  166. });
  167. export default app;